<?php

namespace Fendx\Doctrine;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr;
use Fendx\Db\EntityManager;
use Fendx\Db\SQLBuilder;
use Fendx\Funcs\FendxPage;

/**
 * Override EntityRepository
 *
 * @Author lichao
 */
class EntityRepositoryOverride
{
    private $repo = null;
    private $db = null;
    private $em = null;
    private $sqlBuilder = null;
    private $tableName = '';
    private $alias = 'self';
    public function __construct(EntityRepository $repo, \Doctrine\ORM\EntityManager $em,  EntityManager $db) {
        $this->repo = $repo;
        $this->db = $db;
        $this->em = $em;
        if (\Fendx\Debug::isDebug()) {
            $this->tableName = $this->em->getClassMetadata($this->repo->getClassName())->getTableName();
            $this->sqlBuilder = new SQLBuilder();
        }
    }

    public function setTableAlias(string $alias = 'self') {
        $this->alias = $alias;
    }

    public function getTableAlias(): string {
        return $this->alias;
    }


    /**
     * Finds an entity by its primary key / identifier.
     *
     * @param mixed    $id          The identifier.
     * @param int|null $lockMode    One of the \Doctrine\DBAL\LockMode::* constants
     *                              or NULL if no specific lock mode should be used
     *                              during the search.
     * @param int|null $lockVersion The lock version.
     *
     * @return object|null The entity instance or NULL if the entity can not be found.
     *
     * @psalm-return ?T
     */
    public function find($id, $lockMode = null, $lockVersion = null)
    {
        $startTime = microtime(true);
        $entity = $this->repo->find($id, $lockMode, $lockVersion);

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->tableName);
            $this->sqlBuilder->setConditions([
                'id' => $id
            ]);
            $this->sqlBuilder->setLimit(0, 1);
            $this->db->appendSqlInfo($this->sqlBuilder->getSql(), ['id' => $id], $startTime);
        }
        return $entity;
    }

    /**
     * Finds entities by a set of criteria.
     *
     * @param array      $criteria
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     *
     * @return array The objects.
     *
     * @psalm-return list<T>
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
    {
        $startTime = microtime(true);
        $entities = $this->repo->findBy($criteria, $orderBy, $limit, $offset);

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->tableName);

            $where = Utils::generalWhereByCriteria($criteria);

            $this->sqlBuilder->where($where);
            $this->sqlBuilder->setOrder($orderBy);
            $this->sqlBuilder->setLimit($offset, $limit);

            $this->db->appendSqlInfo($this->sqlBuilder->getSql(), $criteria, $startTime);
        }
        return $entities;
    }

    /**
     * 分页获取数据列表
     * 使用QueryBuilder组件获取，可实现所有功能
     *
     * @param array $where  [['name', 'lichao'], ['id', '>', 5]]
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     * @param int        $hydrationMode  Query::HYDRATE_OBJECT | Query::HYDRATE_ARRAY
     * @return array
     */
    public function findByQueryBuilder(callable $funQueryBuilder, array $orderBy = null, int $limit = null, int $offset = null, bool $cacheable = false, int $hydrationMode = Query::HYDRATE_OBJECT)
    {
        $startTime = microtime(true);
        $qb = $this->em->createQueryBuilder();
        $qb->select($this->alias);
        $qb->from($this->repo->getClassName(), $this->alias);

        $funQueryBuilder($qb);

        $qb->setCacheable($cacheable);

        // orderBy
        if (!empty($orderBy)) {
            $ob = new Expr\OrderBy();
            foreach ($orderBy as $sort => $order) {
                $ob->add(sprintf('%s.%s', $this->alias, $sort), $order);
            }
            $qb->orderBy($ob);
        }

        if (!is_null($limit)) {
            $qb->setMaxResults($limit);
        }

        if (!is_null($offset)) {
            $qb->setFirstResult($offset);
        }

        $query = $qb->getQuery();
        $result = $query->execute(null, $hydrationMode);

        $this->db->appendSqlInfoByQuery($query, $startTime);

        return $result;
    }

    /**
     * 分页获取数据列表
     * 此功能比 $criteria 强大,如$criteria能满足需求推荐用$criteria
     *
     * @param array $where  [['name', 'lichao'], ['id', '>', 5]]
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     * @param int        $hydrationMode  Query::HYDRATE_OBJECT | Query::HYDRATE_ARRAY
     * @return array
     */
    public function findByWhere(array $where = null, array $orderBy = null, int $limit = null, int $offset = null, bool $cacheable = false, int $hydrationMode = Query::HYDRATE_OBJECT): array
    {
        $startTime = microtime(true);
        $qb = $this->em->createQueryBuilder();
        $qb->select($this->alias);
        $qb->from($this->repo->getClassName(), $this->alias);

        !is_null($where) && Utils::generalQueryBuilderByWhere($qb, $this->alias, $where);

        $qb->setCacheable($cacheable);

        // orderBy
        if (!empty($orderBy)) {
            $ob = new Expr\OrderBy();
            foreach ($orderBy as $sort => $order) {
                $ob->add(sprintf('%s.%s', $this->alias, $sort), $order);
            }
            $qb->orderBy($ob);
        }

        if (!is_null($limit)) {
            $qb->setMaxResults($limit);
        }

        if (!is_null($offset)) {
            $qb->setFirstResult($offset);
        }

        $query = $qb->getQuery();
        $result = $query->execute(null, $hydrationMode);

        $this->db->appendSqlInfoByQuery($query, $startTime);

        return $result;
    }

    public function getSumListByWhere($group = '', array $where = null, $fields = "count(1) as total", array $orderBy = null, int $limit = null, int $offset = null): array
    {
        $startTime = microtime(true);
        $qb = $this->em->createQueryBuilder();
        $qb->select($fields);
        $qb->from($this->repo->getClassName(), $this->alias);

        !is_null($where) && Utils::generalQueryBuilderByWhere($qb, $this->alias, $where);

        $group && $qb->groupBy($group);

        // orderBy
        if (!empty($orderBy)) {
            $ob = new Expr\OrderBy();
            foreach ($orderBy as $sort => $order) {
                $ob->add(sprintf('%s.%s', $this->alias, $sort), $order);
            }
            $qb->orderBy($ob);
        }

        if (!is_null($limit)) {
            $qb->setMaxResults($limit);
        }

        if (!is_null($offset)) {
            $qb->setFirstResult($offset);
        }

        $query = $qb->getQuery();
        $result = $query->execute();

        $this->db->appendSqlInfoByQuery($query, $startTime);

        return $result;
    }

    /**
     * Finds a single entity by a set of criteria.
     *
     * @param array      $criteria
     * @param array|null $orderBy
     *
     * @return object|null The entity instance or NULL if the entity can not be found.
     *
     * @psalm-return ?T
     */
    public function findOneBy(array $criteria, array $orderBy = null)
    {
        $startTime = microtime(true);
        $entity = $this->repo->findOneBy($criteria, $orderBy);

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->tableName);

            $this->sqlBuilder->setConditions($criteria);
            $this->sqlBuilder->setOrder($orderBy);
            $this->db->appendSqlInfo($this->sqlBuilder->getSql(), $criteria, $startTime);
        }
        return $entity;
    }

    /**
     * 能过多个ID获取MAP形式数据
     *
     * @param array    $ids
     * @return array
     *
     * @psalm-return ?T
     */
    public function getMapByIds(array $ids): array
    {
        $startTime = microtime(true);
        $entities = $this->repo->findBy([
            'id' => $ids,
        ]);
        $map = [];
        foreach ($entities as $entity) {
            $map[$entity->getId()] = $entity;
        }

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->tableName);
            $this->sqlBuilder->setConditions([
                'id' => $ids
            ]);
            $this->db->appendSqlInfo($this->sqlBuilder->getSql(), ['id' => $ids], $startTime);
        }
        return $map;
    }

    /**
     * Finds all entities in the repository.
     *
     * @return array The entities.
     *
     * @psalm-return list<T>
     */
    public function findAll()
    {
        $startTime = microtime(true);
        $entity = $this->repo->findAll();

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->em->getClassMetadata($this->repo->getClassName())->getTableName());

            $this->db->appendSqlInfo($this->sqlBuilder->getSql(), [], $startTime);
        }
        return $entity;
    }


    /**
     * 统计符合条件数据个数
     *
     * @param array  $criteria
     * @return int
     */
    public function count(array $criteria): int {

        $startTime = microtime(true);
        $total = $this->repo->count($criteria);

        //debug open will record more
        if (\Fendx\Debug::isDebug()) {
            $this->sqlBuilder->clean();
            $this->sqlBuilder->setTable($this->tableName);

            $where = Utils::generalWhereByCriteria($criteria);

            $this->sqlBuilder->where($where);
            $this->db->appendSqlInfo($this->sqlBuilder->getSqlSum(), [], $startTime);
        }
        return $total;
    }

    /**
     * 统计符合条件数据个数
     * 此功能比 $criteria 强大,如 $criteria 能满足需求推荐用 $criteria
     *
     * @param array $where  [['name', 'lichao'], ['id', '>', 5]]
     * @return int
     */
    public function countByWhere(array $where): int
    {
        $startTime = microtime(true);
        $qb = $this->em->createQueryBuilder();
        $qb->select(sprintf('count(%s.id) as count', $this->alias));
        $qb->from($this->repo->getClassName(), $this->alias);

        Utils::generalQueryBuilderByWhere($qb, $this->alias, $where);

        // orderBy
        if (!empty($orderBy)) {
            $ob = new Expr\OrderBy();
            foreach ($orderBy as $sort => $order) {
                $ob->add(sprintf('%s.%s', $this->alias, $sort), $order);
            }
            $qb->orderBy($ob);
        }

        $query = $qb->getQuery();
        $result = $query->getOneOrNullResult();

        if (empty($result)) {
            return 0;
        }

        $this->db->appendSqlInfoByQuery($query, $startTime);

        return (int) $result['count'];
    }

    /**
     * 统计符合条件数据个数
     * 此功能比 $criteria 强大,如 $criteria 能满足需求推荐用 $criteria
     *
     * @param array $where  [['name', 'lichao'], ['id', '>', 5]]
     * @return int
     */
    public function countByQueryBuilder(callable $funQueryBuilder): int
    {
        $startTime = microtime(true);
        $qb = $this->em->createQueryBuilder();
        $qb->select(sprintf('count(%s.id) as count', $this->alias));
        $qb->from($this->repo->getClassName(), $this->alias);

        $funQueryBuilder($qb);

        $query = $qb->getQuery();
        $result = $query->getOneOrNullResult();

        if (empty($result)) {
            return 0;
        }

        $this->db->appendSqlInfoByQuery($query, $startTime);

        return (int) $result['count'];
    }

    /**
     * 分页获取数据列表
     *
     * @param array      $criteria
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     * @return array
     */
    public function getDataList(array $criteria, array $orderBy = null, int $limit = null, int $offset = null) {

        $item = [
            'limit' => $limit,
            'page' => FendxPage::getPage($limit, $offset),
            'pages' => 1,
            'total' => 0,
            'list' => []
        ];

        $item['total'] = $this->count($criteria);
        $item['pages'] = FendxPage::getPages($item['total'], $limit);
        $item['list'] = $this->findBy($criteria, $orderBy, $limit, $offset);

        return $item;
    }

    /**
     * 分页获取数据列表
     *
     * @param array      $criteria
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     * @param int        $hydrationMode  Query::HYDRATE_OBJECT | Query::HYDRATE_ARRAY
     * @return array
     */
    public function getDataListByWhere(array $criteria, array $orderBy = null, int $limit = null, int $offset = null,  bool $cacheable = false, int $hydrationMode = Query::HYDRATE_OBJECT) {

        $item = [
            'limit' => $limit,
            'page' => FendxPage::getPage($limit, $offset),
            'pages' => 1,
            'total' => 0,
            'list' => []
        ];

        $item['total'] = $this->countByWhere($criteria);
        $item['pages'] = FendxPage::getPages($item['total'], $limit);
        $item['list'] = $this->findByWhere($criteria, $orderBy, $limit, $offset, $cacheable, $hydrationMode);

        return $item;
    }

    /**
     * 分页获取数据列表
     *
     * @param array      $criteria
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     * @param int        $hydrationMode  Query::HYDRATE_OBJECT | Query::HYDRATE_ARRAY
     * @return array
     */
    public function getDataListByQueryBuilder(callable $funQueryBuilder, array $orderBy = null, int $limit = null, int $offset = null, bool $cacheable = false, int $hydrationMode = Query::HYDRATE_OBJECT) {

        $item = [
            'limit' => $limit,
            'page' => FendxPage::getPage($limit, $offset),
            'pages' => 1,
            'total' => 0,
            'list' => []
        ];

        $item['total'] = $this->countByQueryBuilder($funQueryBuilder);
        $item['pages'] = FendxPage::getPages($item['total'], $limit);
        $item['list'] = $this->findByQueryBuilder($funQueryBuilder, $orderBy, $limit, $offset, $cacheable, $hydrationMode);

        return $item;
    }


    public function __call($name, $arguments) {
        return $this->repo->$name(...$arguments);
    }

    public function __destruct() {
        $this->em = null;
        $this->db = null;
        $this->repo = null;
        $this->sqlBuilder = null;
    }
}
